/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.catalina.core;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.CometEvent;
import org.apache.catalina.Container;
import org.apache.catalina.Globals;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.StringManager;
import org.apache.catalina.valves.ValveBase;
import org.apache.tomcat.util.buf.MessageBytes;

/**
 * Valve that implements the default basic behavior for the
 * <code>StandardContext</code> container implementation.
 * <p>
 * <b>USAGE CONSTRAINT</b>: This implementation is likely to be useful only
 * when processing HTTP requests.
 * 
 * @author Craig R. McClanahan
 * @version $Revision: 673834 $ $Date: 2008-07-04 06:03:08 +0800 (星期五, 04 七月
 *          2008) $
 */

final class StandardContextValve extends ValveBase {

	// ----------------------------------------------------- Instance Variables

	/**
	 * The descriptive information related to this implementation.
	 */
	private static final String info = "org.apache.catalina.core.StandardContextValve/1.0";

	/**
	 * The string manager for this package.
	 */
	private static final StringManager sm = StringManager
			.getManager(Constants.Package);

	private StandardContext context = null;

	// ------------------------------------------------------------- Properties

	/**
	 * Return descriptive information about this Valve implementation.
	 */
	public String getInfo() {

		return (info);

	}

	// --------------------------------------------------------- Public Methods

	/**
	 * Cast to a StandardContext right away, as it will be needed later.
	 * 
	 * @see org.apache.catalina.Contained#setContainer(org.apache.catalina.Container)
	 */
	public void setContainer(Container container) {
		super.setContainer(container);
		context = (StandardContext) container;
	}

	/**
	 * Select the appropriate child Wrapper to process this request, based on
	 * the specified request URI. If no matching Wrapper can be found, return an
	 * appropriate HTTP error.
	 * 
	 * @param request
	 *            Request to be processed
	 * @param response
	 *            Response to be produced
	 * @param valveContext
	 *            Valve context used to forward to the next Valve
	 * 
	 * @exception IOException
	 *                if an input/output error occurred
	 * @exception ServletException
	 *                if a servlet error occurred
	 */
	public final void invoke(Request request, Response response)
			throws IOException, ServletException {

		// Disallow any direct access to resources under WEB-INF or META-INF
		MessageBytes requestPathMB = request.getRequestPathMB();
		if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
				|| (requestPathMB.equalsIgnoreCase("/META-INF"))
				|| (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
				|| (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
			notFound(response);
			return;
		}

		// Wait if we are reloading
		boolean reloaded = false;
		while (context.getPaused()) {
			reloaded = true;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				;
			}
		}

		// Reloading will have stopped the old webappclassloader and
		// created a new one
		if (reloaded && context.getLoader() != null
				&& context.getLoader().getClassLoader() != null) {
			Thread.currentThread().setContextClassLoader(
					context.getLoader().getClassLoader());
		}

		// Select the Wrapper to be used for this Request
		Wrapper wrapper = request.getWrapper();
		if (wrapper == null) {
			notFound(response);
			return;
		} else if (wrapper.isUnavailable()) {
			// May be as a result of a reload, try and find the new wrapper
			wrapper = (Wrapper) container.findChild(wrapper.getName());
			if (wrapper == null) {
				notFound(response);
				return;
			}
		}

		// Normal request processing
		Object instances[] = context.getApplicationEventListeners();

		ServletRequestEvent event = null;

		if ((instances != null) && (instances.length > 0)) {
			event = new ServletRequestEvent(((StandardContext) container)
					.getServletContext(), request.getRequest());
			// create pre-service event
			for (int i = 0; i < instances.length; i++) {
				if (instances[i] == null)
					continue;
				if (!(instances[i] instanceof ServletRequestListener))
					continue;
				ServletRequestListener listener = (ServletRequestListener) instances[i];
				try {
					listener.requestInitialized(event);
				} catch (Throwable t) {
					container
							.getLogger()
							.error(
									sm
											.getString(
													"standardContext.requestListener.requestInit",
													instances[i].getClass()
															.getName()), t);
					ServletRequest sreq = request.getRequest();
					sreq.setAttribute(Globals.EXCEPTION_ATTR, t);
					return;
				}
			}
		}

		wrapper.getPipeline().getFirst().invoke(request, response);

		if ((instances != null) && (instances.length > 0)) {
			// create post-service event
			for (int i = 0; i < instances.length; i++) {
				if (instances[i] == null)
					continue;
				if (!(instances[i] instanceof ServletRequestListener))
					continue;
				ServletRequestListener listener = (ServletRequestListener) instances[i];
				try {
					listener.requestDestroyed(event);
				} catch (Throwable t) {
					container
							.getLogger()
							.error(
									sm
											.getString(
													"standardContext.requestListener.requestDestroy",
													instances[i].getClass()
															.getName()), t);
					ServletRequest sreq = request.getRequest();
					sreq.setAttribute(Globals.EXCEPTION_ATTR, t);
				}
			}
		}

	}

	/**
	 * Select the appropriate child Wrapper to process this request, based on
	 * the specified request URI. If no matching Wrapper can be found, return an
	 * appropriate HTTP error.
	 * 
	 * @param request
	 *            Request to be processed
	 * @param response
	 *            Response to be produced
	 * @param valveContext
	 *            Valve context used to forward to the next Valve
	 * 
	 * @exception IOException
	 *                if an input/output error occurred
	 * @exception ServletException
	 *                if a servlet error occurred
	 */
	public final void event(Request request, Response response, CometEvent event)
			throws IOException, ServletException {

		// Select the Wrapper to be used for this Request
		Wrapper wrapper = request.getWrapper();

		// Normal request processing
		// FIXME: This could be an addition to the core API too
		/*
		 * Object instances[] = context.getApplicationEventListeners();
		 * 
		 * ServletRequestEvent event = null;
		 * 
		 * if ((instances != null) && (instances.length > 0)) { event = new
		 * ServletRequestEvent (((StandardContext)
		 * container).getServletContext(), request.getRequest()); // create
		 * pre-service event for (int i = 0; i < instances.length; i++) { if
		 * (instances[i] == null) continue; if (!(instances[i] instanceof
		 * ServletRequestListener)) continue; ServletRequestListener listener =
		 * (ServletRequestListener) instances[i]; try {
		 * listener.requestInitialized(event); } catch (Throwable t) {
		 * container.getLogger().error(sm.getString("requestListenerValve.requestInit",
		 * instances[i].getClass().getName()), t); ServletRequest sreq =
		 * request.getRequest(); sreq.setAttribute(Globals.EXCEPTION_ATTR,t);
		 * return; } } }
		 */

		wrapper.getPipeline().getFirst().event(request, response, event);

		/*
		 * if ((instances !=null ) && (instances.length > 0)) { // create
		 * post-service event for (int i = 0; i < instances.length; i++) { if
		 * (instances[i] == null) continue; if (!(instances[i] instanceof
		 * ServletRequestListener)) continue; ServletRequestListener listener =
		 * (ServletRequestListener) instances[i]; try {
		 * listener.requestDestroyed(event); } catch (Throwable t) {
		 * container.getLogger().error(sm.getString("requestListenerValve.requestDestroy",
		 * instances[i].getClass().getName()), t); ServletRequest sreq =
		 * request.getRequest(); sreq.setAttribute(Globals.EXCEPTION_ATTR,t); } } }
		 */

	}

	// -------------------------------------------------------- Private Methods

	/**
	 * Report a "not found" error for the specified resource. FIXME: We should
	 * really be using the error reporting settings for this web application,
	 * but currently that code runs at the wrapper level rather than the context
	 * level.
	 * 
	 * @param response
	 *            The response we are creating
	 */
	private void notFound(HttpServletResponse response) {

		try {
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
		} catch (IllegalStateException e) {
			;
		} catch (IOException e) {
			;
		}

	}

}
